package cn.flying.cloud.base.common.utils.crypto;

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 国密算法 - SM2加密工具：用来替换RSA，属于非对称加密，需要公钥和私钥
 * 它是基于椭圆曲线密码的公钥密码算法标准，其秘钥长度256bit，包含数字签名、密钥交换和公钥加密，用于替换RSA/DH/ECDSA/ECDH等国际算法。
 * 可以满足电子认证服务系统等应用需求，由国家密码管理局于2010年12月17号发布。
 * SM2采用的是ECC 256位的一种，其安全强度比RSA 2048位高，且运算速度快于RSA
 *
 * @author: admin
 * @date: 2025年01月21日 17:37
 * @version: 1.0
 */
public class SM2Util {
    private static final Logger logger = LoggerFactory.getLogger(SM2Util.class);
    private static final String STD_NAME = "sm2p256v1";
    private static final String ALGORITHM = "EC";
    public static final String PUBLIC_KEY = "publicKey";

    public static final String PRIVATE_KEY = "privateKey";

    static {
        // 防止内存中出现多次BouncyCastleProvider的实例
        if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 生成公私钥对
     *
     * @return
     * @throws Exception
     */
    public static Map<String, String> generateKeyPair(String keySeed) {
        try {
            SecureRandom random = new SecureRandom(keySeed.getBytes(StandardCharsets.UTF_8));
            ECGenParameterSpec sm2Spec = new ECGenParameterSpec(STD_NAME);
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, new BouncyCastleProvider());
            keyPairGenerator.initialize(sm2Spec);
            keyPairGenerator.initialize(sm2Spec, random);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey publicKey = keyPair.getPublic();
            String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
            String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
            Map<String, String> keys = new HashMap<>();
            keys.put(PUBLIC_KEY, publicKeyStr);
            keys.put(PRIVATE_KEY, privateKeyStr);
            return keys;
        } catch (Exception e) {
            logger.error("公私钥对生成失败", e);
            return new HashMap<>();
        }
    }

    /**
     * 将Base64编码的公钥串，转化为公钥对象
     *
     * @param pub
     * @return
     */
    public static PublicKey generatePublicKey(String pub) {
        try {
            byte[] decode = Base64.getDecoder().decode(pub);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decode);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, new BouncyCastleProvider());
            return keyFactory.generatePublic(publicKeySpec);
        } catch (Exception e) {
            logger.error("将Base64转码的公钥串，转化为公钥对象异常：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 将Base64编码的私钥串，转化为私钥对象
     *
     * @param pri
     * @return
     */
    public static PrivateKey generatePrivateKey(String pri) {
        try {
            byte[] decode = Base64.getDecoder().decode(pri);
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, new BouncyCastleProvider());
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            logger.error("将Base64转码的私钥串，转化为私钥对象异常：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 根据publicKey对原始数据data，使用SM2加密
     *
     * @param data
     * @param pub
     * @return
     */
    public static String encrypt(String data, String pub) {
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        PublicKey publicKey = generatePublicKey(pub);
        ECPublicKeyParameters publicKeyParameters = getEcPublicKeyParameters(publicKey);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
        try {
            return Base64.getEncoder().encodeToString(sm2Engine.processBlock(dataBytes, 0, dataBytes.length));
        } catch (InvalidCipherTextException e) {
            logger.error("SM2加密失败:{}", e.getMessage(), e);
            return null;
        }
    }

//    /**
//     * 根据publicKey对原始数据data，使用SM2加密
//     *
//     * @param data
//     * @param publicKey
//     * @return
//     */
//    public static String encrypt(String data, PublicKey publicKey) {
//        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
//        ECPublicKeyParameters publicKeyParameters = getEcPublicKeyParameters(publicKey);
//        SM2Engine sm2Engine = new SM2Engine();
//        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
//        try {
//            return Base64.getEncoder().encodeToString(sm2Engine.processBlock(bytes, 0, bytes.length));
//        } catch (InvalidCipherTextException e) {
//            logger.error("SM2加密失败:{}", e.getMessage(), e);
//            return null;
//        }
//    }

    private static ECPublicKeyParameters getEcPublicKeyParameters(PublicKey publicKey) {
        if (!(publicKey instanceof BCECPublicKey)) {
            return null;
        }
        BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
        ECParameterSpec parameterSpec = bcecPublicKey.getParameters();
        ECDomainParameters parameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), parameterSpec.getN());
        return new ECPublicKeyParameters(bcecPublicKey.getQ(), parameters);
    }

    /**
     * 根据privateKey对加密数据encode data，使用SM2解密
     *
     * @param data
     * @param pri
     * @return
     */
    public static String decrypt(String data, String pri) {
        byte[] dataBytes = Base64.getDecoder().decode(data);
        PrivateKey privateKey = generatePrivateKey(pri);
        ECPrivateKeyParameters privateKeyParameters = getEcPrivateKeyParameters(privateKey);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, privateKeyParameters);
        try {
            return new String(sm2Engine.processBlock(dataBytes, 0, dataBytes.length));
        } catch (InvalidCipherTextException e) {
            logger.error("SM2解密失败:{}", e.getMessage(), e);
            return null;
        }
    }

//    /**
//     * 根据privateKey对加密数据encode data，使用SM2解密
//     *
//     * @param data
//     * @param privateKey
//     * @return
//     */
//    public static String decrypt(String data, PrivateKey privateKey) {
//        byte[] bytes = Base64.getDecoder().decode(data);
//        ECPrivateKeyParameters privateKeyParameters = getEcPrivateKeyParameters(privateKey);
//        SM2Engine sm2Engine = new SM2Engine();
//        sm2Engine.init(false, privateKeyParameters);
//        try {
//            return new String(sm2Engine.processBlock(bytes, 0, bytes.length));
//        } catch (InvalidCipherTextException e) {
//            logger.error("SM2解密失败:{}", e.getMessage(), e);
//            return null;
//        }
//    }

    private static ECPrivateKeyParameters getEcPrivateKeyParameters(PrivateKey privateKey) {
        if (!(privateKey instanceof BCECPrivateKey)) {
            return null;
        }
        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
        ECParameterSpec parameterSpec = bcecPrivateKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), parameterSpec.getN());
        return new ECPrivateKeyParameters(bcecPrivateKey.getD(), domainParameters);
    }

    /**
     * 私钥签名 返回Base64编码
     *
     * @param data
     * @param pri
     * @return
     * @throws Exception
     */
    public static String sign(String data, String pri) throws Exception {
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        SM2Signer signer = new SM2Signer();
        signer.init(true, getEcPrivateKeyParameters(generatePrivateKey(pri)));
        signer.update(dataBytes, 0, dataBytes.length);
        return Base64.getEncoder().encodeToString(signer.generateSignature());
    }

    /**
     * 公钥验签
     *
     * @param data
     * @param pub
     * @param sign
     * @return
     * @throws Exception
     */
    public static boolean verify(String data, String pub, String sign) throws Exception {
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        SM2Signer signer = new SM2Signer();
        signer.init(false, getEcPublicKeyParameters(generatePublicKey(pub)));
        signer.update(dataBytes, 0, dataBytes.length);
        return signer.verifySignature(Base64.getDecoder().decode(sign));
    }

//    public static void main(String[] args) throws Exception {
//        /**加密解密过程：
//         * 1.生成密钥对：公钥和私钥
//         * 2.利用原文+公钥加密
//         * 3.利用密文+私钥解密
//         */
//        String data = "国密算法：即我国自主研制的算法，摆脱对国外的技术过度依赖，国密即国家密码局认定的国产密码算法。以下是具体的对应关系：对称算法：AES、DES、SM4；非对称算法：RSA、SM2；摘要/杂凑算法：MD5、SHA-I、SM3";
//        String keySrc = UUID.randomUUID().toString();
//        Map<String, String> keyPair = Sm2Util.generateKeyPair(keySrc);
//        String publicKey = keyPair.get(PUBLIC_KEY);
//        String privateKey = keyPair.get(PRIVATE_KEY);
//
//        System.out.println("keySrc:" + keySrc);
//        // 公钥:
//        System.out.println("公钥:" + publicKey);
//        // 私钥:
//        System.out.println("私钥:" + privateKey);
//
//        String cipherText = Sm2Util.encrypt(data, publicKey);
//        System.out.println("密文:" + cipherText);
//
//        String deStr = Sm2Util.decrypt(cipherText, privateKey);
//        System.out.println("明文:" + deStr);
//    }
}
